const DECIMAL_ZEROS = (function (len) {
  const list: string[] = [];
  for (let i = 0; i < len; i++) {
    list.push('0');
  }
  return list.join('');
})(10);
type DecimalOptions = { value: string; precision: number };

export class Decimal {
  /**
   * 值
   * @private
   */
  private readonly value: string;

  /**
   * 小数点位置
   * @private
   */
  private readonly precision: number;

  public static readonly ZERO = new Decimal('0');

  public static readonly ONE = new Decimal('1');

  public static readonly TEN = new Decimal('10');

  public static readonly HUNDRED = new Decimal('100');

  public constructor(value: string | number | Decimal | DecimalOptions) {
    if (typeof value === 'object') {
      // @ts-ignore
      this.precision = value.precision;
      // @ts-ignore
      this.value = value.value;
    } else {
      value = String(value);
      const vs = value.split('.');
      if (vs.length === 1) {
        this.precision = 0;
        this.value = value;
      } else if (vs.length === 2) {
        const int = vs[0];
        const decimal = vs[1];
        this.precision = decimal.length;
        this.value = (int || '0') + decimal;
      } else {
        throw '格式错误';
      }
    }
  }

  public add(v: string | number | Decimal) {
    const decimal: Decimal = new Decimal(v);
    let precision;
    if (decimal.precision > this.precision) {
      precision = decimal.precision;
      const nv = (BigInt(decimal.value) + Decimal.scale(this.value, decimal.precision - this.precision)).toString();
      return new Decimal({ value: nv, precision });
    }
    precision = this.precision;
    const nv = (BigInt(this.value) + Decimal.scale(decimal.value, this.precision - decimal.precision)).toString();
    return new Decimal({ value: nv, precision });
  }

  public sub(v: string | number | Decimal) {
    const decimal: Decimal = new Decimal(v);
    let precision;
    if (decimal.precision > this.precision) {
      precision = decimal.precision;
      const nv = (BigInt(decimal.value) - Decimal.scale(this.value, decimal.precision - this.precision)).toString();
      return new Decimal({ value: nv, precision });
    }
    precision = this.precision;
    const nv = (BigInt(this.value) - Decimal.scale(decimal.value, this.precision - decimal.precision)).toString();
    return new Decimal({ value: nv, precision });
  }

  public mul(v: string | number | Decimal) {
    const decimal: Decimal = new Decimal(v);
    let precision;
    if (decimal.precision > this.precision) {
      precision = decimal.precision;
      const nv = (BigInt(decimal.value) * Decimal.scale(this.value, decimal.precision - this.precision)).toString();
      return new Decimal({ value: nv, precision });
    }
    precision = this.precision;
    const nv = (BigInt(this.value) * Decimal.scale(decimal.value, this.precision - decimal.precision)).toString();
    return new Decimal({ value: nv, precision });
  }

  public div(v: string | number | Decimal) {
    const decimal: Decimal = new Decimal(v);
    let precision;
    if (decimal.precision > this.precision) {
      precision = decimal.precision;
      const nv = (BigInt(decimal.value) / Decimal.scale(this.value, decimal.precision - this.precision)).toString();
      return new Decimal({ value: nv, precision });
    }
    precision = this.precision;
    const nv = (BigInt(this.value) / Decimal.scale(decimal.value, this.precision - decimal.precision)).toString();
    return new Decimal({ value: nv, precision });
  }

  public equals(obj: any) {
    if (typeof obj === 'number') {
      return this.toNumber() === obj;
    }
    if (typeof obj === 'string') {
      return this.value === obj;
    }
    if (obj instanceof Decimal) {
      return BigInt(this.value) === BigInt(obj.value);
    }
    return BigInt(this.value) === new Decimal(obj).value;
  }

  public toString(radix?: number) {
    if (radix === undefined || radix !== 10) {
      return BigInt(this.value).toString(radix);
    }
    const str = this.value;
    if (this.precision === 0) {
      return str;
    }
    const index = str.length - this.precision;
    return `${str.slice(0, index)}.${str.slice(index)}`;
  }

  public toNumber() {
    return parseFloat(this.toString());
  }

  public static add(...args) {
    let result = Decimal.ZERO;
    for (const value of args) {
      if (value) {
        result = result.add(value);
      }
    }
    return result;
  }

  public static sub(subtrahend: any, ...args) {
    let result = new Decimal(subtrahend ?? '0');
    for (const arg of args) {
      result = result.sub(arg);
    }
    return result;
  }

  public static mul(...args) {
    let result = Decimal.ONE;
    for (const value of args) {
      if (value) {
        result = result.mul(value);
      }
    }
    return result;
  }

  public static div(divisor: any, ...args) {
    if (!divisor || BigInt(divisor.value) === 0n) {
      return null;
    }
    let result = Number(divisor);
    for (const arg of args) {
      if (isNotNumber(arg)) {
        return null;
      }
      result /= arg;
    }
    return result;
  }

  private static scale(v: string, len: number) {
    return BigInt(len <= 0 ? v : v + DECIMAL_ZEROS.slice(0, len));
  }
}
